﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace MedicWave.Visualization3d
{
    delegate void OnPaintHandler(PaintEventArgs args);
    /// <summary>
    /// A generic DirectX control that can be used in a form
    /// </summary>
    partial class DXControl : UserControl
    {
        protected Device d3dDevice = null;
        protected Caps deviceCaps;
        protected CreateFlags deviceCreateFlags;
        protected PresentParameters currentPresentParameters;
        protected bool isDeviceLost = false;
        protected bool isWindowed = true;
        protected bool m_bUseShaders = false;

        //debug log, useful for outputting data about what is happening in the control
        internal Log debugLog = new Log("DXControlLog.txt");

        //stored windowed data
        //protected Rectangle windowedRectangle;
        protected Form fullscreenForm;


        protected override void OnPaintBackground(PaintEventArgs e)
        {
            if(d3dDevice==null)
                base.OnPaintBackground(e);
        }


        public DXControl()
        {
            InitializeComponent();

        }

        void DXControl_Resize(object sender, EventArgs e)
        {
            debugLog.BeginBlock("DXControl_Resize called");
            if (d3dDevice != null)
            {
                Control control = (Control)sender;
                //detect minimizing window
                if (control.Width == 0 && control.Height == 0)
                {
                    debugLog.EndBlock("DXControl_Resize succeeded, minimizing window");
                    return;
                }
                //resize if there was a change in size
                if (control.Size.Width != currentPresentParameters.BackBufferWidth ||
                    control.Size.Height != currentPresentParameters.BackBufferHeight)
                {
                    debugLog.Write("New size, resetting the device");
                    ResetDevice();
                }
                else
                {
                    debugLog.Write("Old size, not doing anything(probably back from minimized)");
                }
            }
            debugLog.EndBlock("DXControl_Resize succeeded");

        }

        #region Device initialization

        /// <summary>
        /// Initializes DirectX (creates a device with correct parameters)
        /// </summary>
        protected bool SetupDirectX()
        {
            debugLog.BeginBlock("SetupDirectX called");
            currentPresentParameters = new PresentParameters();
            try
            {
                int adapterNum = 0;

                DisplayMode displayMode = Manager.Adapters[adapterNum].CurrentDisplayMode;

                deviceCaps = Manager.GetDeviceCaps(adapterNum, DeviceType.Hardware);

                //Check for hardware T&L
                if (deviceCaps.DeviceCaps.SupportsHardwareTransformAndLight)
                {
                    deviceCreateFlags = CreateFlags.HardwareVertexProcessing;
                    debugLog.Write("Using hardware vertex processing");
                }
                else
                {
                    deviceCreateFlags = CreateFlags.SoftwareVertexProcessing;
                    debugLog.Write("Using software vertex processing");
                }
                if (deviceCaps.VertexShaderVersion >= new Version(2, 0) && deviceCaps.PixelShaderVersion >= new Version(2, 0))
                {
                    m_bUseShaders = true;
                    debugLog.Write("Using shaders");
                }
                else
                {
                    debugLog.Write("Not using shaders");
                }

                //find correct depth stencil format
                if (Manager.CheckDeviceFormat(adapterNum, DeviceType.Hardware, displayMode.Format, Usage.DepthStencil,
                    ResourceType.Surface, DepthFormat.D24X8))
                {
                    currentPresentParameters.AutoDepthStencilFormat = DepthFormat.D24X8;
                    debugLog.Write("Using D24X8 depth stencil format");
                }
                else if (Manager.CheckDeviceFormat(adapterNum, DeviceType.Hardware, displayMode.Format, Usage.DepthStencil,
                    ResourceType.Surface, DepthFormat.D24S8))
                {
                    currentPresentParameters.AutoDepthStencilFormat = DepthFormat.D24S8;
                    debugLog.Write("Using D24S8 depth stencil format");
                }

                else if (Manager.CheckDeviceFormat(adapterNum, DeviceType.Hardware, displayMode.Format, Usage.DepthStencil,
               ResourceType.Surface, DepthFormat.D16))
                {
                    currentPresentParameters.AutoDepthStencilFormat = DepthFormat.D16;
                    debugLog.Write("Using D16 depth stencil format");
                }
                else
                {
                    //error, no depth buffer, that won't work
                    debugLog.EndBlock("SetupDirectX finished, failed(no depthstencil)");
                    return false;
                }
                currentPresentParameters.BackBufferCount = 2;
                currentPresentParameters.BackBufferFormat = displayMode.Format;
                currentPresentParameters.BackBufferWidth = this.ClientRectangle.Width;
                currentPresentParameters.BackBufferHeight = this.ClientRectangle.Height;
                currentPresentParameters.EnableAutoDepthStencil = true;
                currentPresentParameters.DeviceWindow = this;

                //int result;
                //int qualityLevels=0;
                //if (false&&Manager.CheckDeviceMultiSampleType(adapterNum, DeviceType.Hardware, displayMode.Format, true, MultiSampleType.FourSamples, out result, out qualityLevels))
                //{
                //    currentPresentParameters.MultiSample = MultiSampleType.FourSamples;
                //    currentPresentParameters.MultiSampleQuality = qualityLevels - 1;
                //}
                //else
                //{
                currentPresentParameters.MultiSample = MultiSampleType.None;
                currentPresentParameters.MultiSampleQuality = 0;
                //}

                currentPresentParameters.PresentationInterval = PresentInterval.One;
                currentPresentParameters.PresentFlag = PresentFlag.DiscardDepthStencil;
                currentPresentParameters.SwapEffect = SwapEffect.Discard;
                currentPresentParameters.Windowed = true;



                //create the device

                d3dDevice = new Device(adapterNum, DeviceType.Hardware, this, deviceCreateFlags, currentPresentParameters);
                //d3dDevice = new Device(adapterNum, DeviceType.Reference, this, CreateFlags.SoftwareVertexProcessing, currentPresentParameters);

                //enable
                d3dDevice.DeviceReset += new EventHandler(OnResetDevice);
                d3dDevice.DeviceLost += new EventHandler(OnLostDevice);
                d3dDevice.Disposing += new EventHandler(OnDestroyDevice);
                d3dDevice.DeviceResizing += new CancelEventHandler(d3dDevice_DeviceResizing);

                //initialize required objects
                OnCreateDevice(this, null);
                OnResetDevice(this, null);

                debugLog.EndBlock("SetupDirectX succeeded");
                return true;

            }
            catch (DirectXException e)
            {
                d3dDevice = null;
                MessageBox.Show(e.ToString());


                debugLog.Write(e.ToString());
                
                System.Windows.Forms.Label label = new System.Windows.Forms.Label();
                label.Font = new System.Drawing.Font("Microsoft Sans Serif", 20F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
                label.Location = new System.Drawing.Point(50, 20);
                label.Name = "lblError";
                label.Size = new System.Drawing.Size(400, 200);
                label.TabIndex = 1;
                label.BackColor = this.BackColor;
                label.Text = "Could not create compatible Direct3D device.";
                this.Controls.Add(label);
                return false;
            }
            catch
            {
                d3dDevice = null;

                DisableControl("Could not create compatible Direct3D device.");
                
                return false;
            }
        }

        void d3dDevice_DeviceResizing(object sender, CancelEventArgs e)
        {
            e.Cancel = true;
            debugLog.Write("d3dDevice_DeviceResizing called, suppressed automatic reset");
        }


        protected void DisableControl(string text)
        {
            debugLog.Write(text);
            System.Windows.Forms.Label label = new System.Windows.Forms.Label();
            label.Font = new System.Drawing.Font("Microsoft Sans Serif", 20F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
            label.Location = new System.Drawing.Point(50, 20);
            label.Name = "lblError";
            label.Size = new System.Drawing.Size(400, 200);
            label.TabIndex = 1;
            label.BackColor = this.BackColor;
            label.Text = text;
            this.Controls.Add(label);
        }

        /// <summary>
        /// Resets the device, this is called when:
        /// -the device was lost
        /// -control is resizing
        /// -fullscreen/windowed transition
        /// </summary>
        protected void ResetDevice()
        {
            debugLog.BeginBlock("ResetDevice called");
            currentPresentParameters.Windowed = true;
            if (isWindowed)
            {
                currentPresentParameters.BackBufferWidth = this.Size.Width;
                currentPresentParameters.BackBufferHeight = this.Size.Height;
                currentPresentParameters.DeviceWindow = this;
            }
            else
            {
                int adapterNum = 0;
                DisplayMode displayMode = Manager.Adapters[adapterNum].CurrentDisplayMode;
                currentPresentParameters.BackBufferWidth = fullscreenForm.Size.Width;
                currentPresentParameters.BackBufferHeight = fullscreenForm.Size.Height;
                currentPresentParameters.FullScreenRefreshRateInHz = displayMode.RefreshRate;
                currentPresentParameters.DeviceWindow = fullscreenForm;

            }

            d3dDevice.Reset(currentPresentParameters);
            isDeviceLost = false;
            this.Refresh();
            debugLog.EndBlock("ResetDevice succeeded");
        }



        /// <summary>
        /// Tries to regain a lost device
        /// </summary>
        protected bool RegainLostDevice()
        {
            // Check for lost device
            debugLog.Write("RegainLostDevice called");
            int result;
            if (!d3dDevice.CheckCooperativeLevel(out result))
            {
                ResultCode code = (ResultCode)result;//m_device.CheckCooperativeLevelResult();
                if (code == ResultCode.DeviceLost)
                {
                    // The device has been lost but cannot be reset at this time.  
                    // So wait until it can be reset.
                    System.Threading.Thread.Sleep(50);
                    debugLog.Write("RegainLostDevice failed");
                    return false;
                }
                try
                {
                    ResetDevice();
                    debugLog.Write("RegainLostDevice succeeded");
                    return true;
                }
                catch (DeviceLostException)
                {

                    // The device was lost again, so continue waiting until it can be reset.
                    System.Threading.Thread.Sleep(50);
                }
                debugLog.Write("RegainLostDevice failed");
                return false;
            }
            debugLog.Write("RegainLostDevice failed");
            return false;
        }


        public void ToggleFullscreen()
        {
            return;
            //not working right now
            isWindowed = !isWindowed;
            if (isWindowed == false)
            {
                //go to fullscreen, create a separate topmost form for rendering
                fullscreenForm = new Form();
                //fullscreenForm.WindowState = FormWindowState.Normal;
                //fullscreenForm.FormBorderStyle = FormBorderStyle.None;
                fullscreenForm.TopMost = true;
                //fullscreenForm.TopLevel

                fullscreenForm.Paint += new PaintEventHandler(fullscreenForm_Paint);


            }
            else
            {
                if (fullscreenForm != null)
                {
                    //delete fullscreen form
                    fullscreenForm.Dispose();
                    fullscreenForm = null;
                }
            }
            ResetDevice();
        }

        void fullscreenForm_Paint(object sender, PaintEventArgs e)
        {
            BeginInvoke(new OnPaintHandler(this.OnPaint), e);
        }


        #endregion

        #region Device callbacks

        protected virtual void OnCreateDevice(object obj, EventArgs args)
        {
            debugLog.Write("OnCreateDevice called");
        }
        protected virtual void OnResetDevice(object obj, EventArgs args)
        {
            debugLog.Write("OnResetDevice called");
        }

        protected virtual void OnLostDevice(object obj, EventArgs args)
        {
            debugLog.Write("OnLostDevice called");
        }
        protected virtual void OnDestroyDevice(object obj, EventArgs args)
        {
            debugLog.Write("OnDestroyDevice called");
        }

        #endregion

        /// <summary>
        /// Renders control using DirectX
        /// </summary>
        protected virtual void RenderControl()
        {
            d3dDevice.Clear(ClearFlags.Target, Color.Aquamarine, 1, 0);
        }

        /// <summary>
        /// Handles WM_PAINT event and renders control if it's ok to render
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPaint(PaintEventArgs e)
        {
            //base.OnPaint(e);
            if (ParentForm == null || d3dDevice==null)
                base.OnPaint(e);
            else if (this.ParentForm.WindowState == FormWindowState.Minimized)
                return;
            else if (isDeviceLost)
            {
                if (!RegainLostDevice())
                {
                    this.Refresh();
                    return;
                }

            }


            ////ok, every thing's's fine so we can render
            //try
            //{
                RenderD3D();

            //}
            //catch (DeviceLostException)
            //{
            //    //device has been lost
            //    debugLog.Write("Device has been lost!");
            //    isDeviceLost = true;
            //}
        }
        /// <summary>
        /// Begins scene, renders control's content and presents it to the screen
        /// </summary>
        protected void RenderD3D()
        {
            ////ok, every thing's's fine so we can render
            //try
            //{
                if (d3dDevice == null) return;

                d3dDevice.BeginScene();
                RenderControl();

                d3dDevice.EndScene();
                d3dDevice.Present();

            //}
            //catch (DeviceLostException)
            //{
            //    //device has been lost
            //    debugLog.Write("Device has been lost!");
            //    isDeviceLost = true;
            //}
            
        }
        private void DXControl_Load(object sender, EventArgs e)
        {
            SetupDirectX();
        }


    }
}
